// http://www.cocoadev.com/index.pl?BaseSixtyFour
#import <Foundation/Foundation.h>

static const char encodingTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

@implementation NSData (Base64)

+ (NSData *)dataWithBase64EncodedString:(NSString *)string
{
//	if (string == nil)
//		[NSException raise:NSInvalidArgumentException format:nil];
//	if ([string length] == 0)
//		return [NSData data];
//	
//	static char *decodingTable = NULL;
//	if (decodingTable == NULL)
//	{
//		decodingTable = malloc(256);
//		if (decodingTable == NULL)
//			return nil;
//		memset(decodingTable, CHAR_MAX, 256);
//		NSUInteger i;
//		for (i = 0; i < 64; i++)
//			decodingTable[(short)encodingTable[i]] = i;
//	}
//	
//	const char *characters = [string cStringUsingEncoding:NSASCIIStringEncoding];
//	if (characters == NULL)     //  Not an ASCII string!
//		return nil;
//	char *bytes = malloc((([string length] + 3) / 4) * 3);
//	if (bytes == NULL)
//		return nil;
//	NSUInteger length = 0;
//
//	NSUInteger i = 0;
//	while (YES)
//	{
//		char buffer[4];
//		short bufferLength;
//		for (bufferLength = 0; bufferLength < 4; i++)
//		{
//			if (characters[i] == '\0')
//				break;
//			if (isspace(characters[i]) || characters[i] == '=')
//				continue;
//			buffer[bufferLength] = decodingTable[(short)characters[i]];
//			if (buffer[bufferLength++] == CHAR_MAX)      //  Illegal character!
//			{
//				free(bytes);
//				return nil;
//			}
//		}
//		
//		if (bufferLength == 0)
//			break;
//		if (bufferLength == 1)      //  At least two characters are needed to produce one byte!
//		{
//			free(bytes);
//			return nil;
//		}
//		
//		//  Decode the characters in the buffer to bytes.
//		bytes[length++] = (buffer[0] << 2) | (buffer[1] >> 4);
//		if (bufferLength > 2)
//			bytes[length++] = (buffer[1] << 4) | (buffer[2] >> 2);
//		if (bufferLength > 3)
//			bytes[length++] = (buffer[2] << 6) | buffer[3];
//	}
//	
//	realloc(bytes, length);
//	return [NSData dataWithBytesNoCopy:bytes length:length];
    
    const char lookup[] =
    {
        99, 99, 99, 99, 99,99, 99, 99, 99, 99,99, 99, 99, 99, 99,99,
        99, 99, 99, 99, 99,99, 99, 99, 99, 99,99, 99, 99, 99, 99,99,
        99, 99, 99, 99, 99,99, 99, 99, 99, 99,99, 62, 99, 99, 99,63,
        52, 53, 54, 55, 56,57, 58, 59, 60, 61,99, 99, 99, 99, 99,99,
        99,  0,  1,  2,  3, 4,  5,  6,  7,  8, 9, 10, 11, 12, 13,14,
        15, 16, 17, 18, 19,20, 21, 22, 23, 24,25, 99, 99, 99, 99,99,
        99, 26, 27, 28, 29,30, 31, 32, 33, 34,35, 36, 37, 38, 39,40,
        41, 42, 43, 44, 45,46, 47, 48, 49, 50,51, 99, 99, 99, 99,99
    };
    
    NSData *inputData = [string dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
    long long inputLength = [inputData length];
    const unsigned char *inputBytes = [inputData bytes];
    
    long long maxOutputLength = (inputLength /4 + 1) * 3;
    NSMutableData *outputData = [NSMutableData dataWithLength:maxOutputLength];
    unsigned char *outputBytes = (unsigned char *)[outputData mutableBytes];
    
    int accumulator = 0;
    long long outputLength =0;
    unsigned char accumulated[] = {0,0, 0, 0};
    for (long long i = 0; i < inputLength; i++)
    {
        unsigned char decoded = lookup[inputBytes[i] &0x7F];
        if (decoded != 99)
        {
            accumulated[accumulator] = decoded;
            if (accumulator == 3)
            {
                outputBytes[outputLength++] = (accumulated[0] <<2) | (accumulated[1] >>4);
                outputBytes[outputLength++] = (accumulated[1] <<4) | (accumulated[2] >>2);
                outputBytes[outputLength++] = (accumulated[2] <<6) | accumulated[3];
            }
            accumulator = (accumulator +1) % 4;
        }
    }
    
    //handle left-over data
    if (accumulator > 0) outputBytes[outputLength] = (accumulated[0] <<2) | (accumulated[1] >>4);
    if (accumulator > 1) outputBytes[++outputLength] = (accumulated[1] <<4) | (accumulated[2] >>2);
    if (accumulator > 2) outputLength++;
    
    //truncate data to match actual output length
    outputData.length = outputLength;
    return outputLength? outputData: nil;
}


- (NSString *)base64EncodedStringWithWrapWidth:(NSUInteger)wrapWidth
{
    //ensure wrapWidth is a multiple of 4
    wrapWidth = (wrapWidth /4) * 4;
    
    const char lookup[] ="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    
    long long inputLength = [self length];
    const unsigned char *inputBytes = [self bytes];
    
    long long maxOutputLength = (inputLength /3 + 1) * 4;
    maxOutputLength += wrapWidth? (maxOutputLength / wrapWidth) *2: 0;
    unsigned char *outputBytes = (unsigned char *)malloc(maxOutputLength);
    
    long long i;
    long long outputLength =0;
    for (i = 0; i < inputLength -2; i += 3)
    {
        outputBytes[outputLength++] = lookup[(inputBytes[i] &0xFC) >> 2];
        outputBytes[outputLength++] = lookup[((inputBytes[i] &0x03) << 4) | ((inputBytes[i +1] & 0xF0) >>4)];
        outputBytes[outputLength++] = lookup[((inputBytes[i +1] & 0x0F) <<2) | ((inputBytes[i + 2] & 0xC0) >> 6)];
        outputBytes[outputLength++] = lookup[inputBytes[i +2] & 0x3F];
        
        //add line break
        if (wrapWidth && (outputLength + 2) % (wrapWidth + 2) == 0)
        {
            outputBytes[outputLength++] ='\r';
            outputBytes[outputLength++] ='\n';
        }
    }
    
    //handle left-over data
    if (i == inputLength - 2)
    {
        // = terminator
        outputBytes[outputLength++] = lookup[(inputBytes[i] &0xFC) >> 2];
        outputBytes[outputLength++] = lookup[((inputBytes[i] &0x03) << 4) | ((inputBytes[i +1] & 0xF0) >>4)];
        outputBytes[outputLength++] = lookup[(inputBytes[i +1] & 0x0F) <<2];
        outputBytes[outputLength++] =  '=';
    }
    else if (i == inputLength -1)
    {
        // == terminator
        outputBytes[outputLength++] = lookup[(inputBytes[i] &0xFC) >> 2];
        outputBytes[outputLength++] = lookup[(inputBytes[i] &0x03) << 4];
        outputBytes[outputLength++] ='=';
        outputBytes[outputLength++] ='=';
    }
    
    //truncate data to match actual output length
    outputBytes =realloc(outputBytes, outputLength);
    NSString *result = [[NSString alloc] initWithBytesNoCopy:outputBytes length:outputLength encoding:NSASCIIStringEncoding freeWhenDone:YES];
    
#if !__has_feature(objc_arc)
    [resultautorelease];
#endif
    
    return (outputLength >= 4)? result: nil;
}

//- (NSString *)base64Encoding;
//{
//	if ([self length] == 0)
//		return @"";
//
//    char *characters = malloc((([self length] + 2) / 3) * 4);
//	if (characters == NULL)
//		return nil;
//	NSUInteger length = 0;
//	
//	NSUInteger i = 0;
//	while (i < [self length])
//	{
//		char buffer[3] = {0,0,0};
//		short bufferLength = 0;
//		while (bufferLength < 3 && i < [self length])
//			buffer[bufferLength++] = ((char *)[self bytes])[i++];
//		
//		//  Encode the bytes in the buffer to four characters, including padding "=" characters if necessary.
//		characters[length++] = encodingTable[(buffer[0] & 0xFC) >> 2];
//		characters[length++] = encodingTable[((buffer[0] & 0x03) << 4) | ((buffer[1] & 0xF0) >> 4)];
//		if (bufferLength > 1)
//			characters[length++] = encodingTable[((buffer[1] & 0x0F) << 2) | ((buffer[2] & 0xC0) >> 6)];
//		else characters[length++] = '=';
//		if (bufferLength > 2)
//			characters[length++] = encodingTable[buffer[2] & 0x3F];
//		else characters[length++] = '=';	
//	}
//	
//    NSString *ret = [NSString stringWithUTF8String:characters];
//    free(characters);
//    return ret;
//}

- (NSString *)base64EncodedString
{
    return [self base64EncodedStringWithWrapWidth:0];
}

- (NSString *)urlsafe_base64_encode{
    NSString *base64 = [self base64Encoding];
    base64 = [base64 stringByReplacingOccurrencesOfString:@"+" withString:@"-"];
    base64 = [base64 stringByReplacingOccurrencesOfString:@"/" withString:@"_"];
    
//    if (![base64 hasSuffix:@"="])
//        base64 = [base64 stringByAppendingString:@"=="];
    
    return base64;
}

@end